package com.michaelbulava.TFS.vcs;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.vcsUtil.VcsUtil;
import com.michaelbulava.TFS.Messages;
import com.michaelbulava.TFS.log.FileLogger;
import com.michaelbulava.TFS.tasks.vc.CheckoutFileIfNeededTask;
import com.michaelbulava.TFS.vcs.exceptions.TFSException;
import com.michaelbulava.TFS.vcs.internal.*;
import com.michaelbulava.TFS.vcs.utils.TFSFileUtil;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.*;
import com.microsoft.tfs.core.clients.versioncontrol.specs.ItemSpec;

import java.util.*;

/**
 * Created by mbulava on 5/6/2015.
 */
public class TFSVcsVFSListener extends VcsVFSListener {
    private final Logger log = FileLogger.getInstance(TFSVcsVFSListener.class);

    private final VcsFileListenerContextHelper myVcsFileListenerContextHelper;

    public TFSVcsVFSListener(Project project, TFSVcs vcs){
        super(project, vcs);
        myVcsFileListenerContextHelper = VcsFileListenerContextHelper.getInstance(myProject);
    }

    private TFSVcs getVcs(){
        return (TFSVcs)myVcs;
    }

    @Override
    protected String getAddTitle() {
        return Messages.getString("FileListener.AddTitle");
    }

    @Override
    protected String getSingleFileAddTitle() {
        return Messages.getString("FileListener.SingleFileAddTitle");
    }

    @Override
    protected String getSingleFileAddPromptTemplate() {
        return null;
    }

//    @Override
//    protected void executeAdd(){
//        try {
//            WorkstationHelper.processByWorkspaces(TFSFileUtil.getFilePaths(myAddedFiles), false, myProject,
//                    new WorkstationHelper.VoidDelegateProcess() {
//                        @Override
//                        public void executeRequest(Workspace workspace, List<FilePath> localPaths) throws TFSException {
//                            StatusProvider.visitByStatus(workspace, localPaths, false, null,
//                                    addExecuteStatusVisitor, myProject);
//                        }
//                    });
//        } catch (TFSException e) {
//            log.error("Error in executeAdd:", e);
//            AbstractVcsHelper.getInstance(myProject).showError(new VcsException(e), TFSVcs.DISPLAY_NAME);
//        } catch(Throwable tr){
//            log.error("Unknown Error", tr);
//        }
//
//        if (!this.myAddedFiles.isEmpty())
//            super.executeAdd();
//    }

    @Override
    protected void performAdding(Collection<VirtualFile> addedFiles, Map<VirtualFile, VirtualFile> copyFromMap) {
        final List<VcsException> errors = new ArrayList<VcsException>();
        try{
            List<FilePath> orphans = WorkstationHelper.processByWorkspaces(TFSFileUtil.getFilePaths(addedFiles), false,
                    myProject, new WorkstationHelper.VoidDelegateProcess() {
                        @Override
                        public void executeRequest(Workspace workspace, List<FilePath> localPaths) throws TFSException {
                            Collection<VcsException> schedulingErrors = SharedOperations.scheduleForAddition(myProject,
                                    workspace, localPaths);
                            errors.addAll(schedulingErrors);
                        }
                    });
            if (!orphans.isEmpty()){
                StringBuilder s = new StringBuilder();
                for (FilePath orpan : orphans) {
                    if (s.length() > 0)
                        s.append("\n");

                    s.append(orpan.getPresentableUrl());
                }
                log.info("Team Foundation Server mappings not found for: " + s.toString());
                errors.add(new VcsException("Team Foundation Server mappings not found for: " + s.toString()));
            }
        } catch (TFSException e) {
            log.error("Error in performAdd:", e);
            errors.add(new VcsException(e));
        }
        if (!errors.isEmpty())
            AbstractVcsHelper.getInstance(this.myProject).showErrors(errors, TFSVcs.VCS_NAME);
    }

    @Override
    protected String getDeleteTitle() {
        return Messages.getString("FileListener.DeleteMultipleTitle");
    }

    @Override
    protected String getSingleFileDeleteTitle() {
        return null;
    }

    @Override
    protected String getSingleFileDeletePromptTemplate() {
        return null;
    }

    @Override
    protected void executeDelete(){
        List<FilePath> deletedFiles = new ArrayList<FilePath>(myDeletedFiles);
        deletedFiles.addAll(myDeletedWithoutConfirmFiles);

        try {
            WorkstationHelper.processByWorkspaces(deletedFiles, false, myProject,
                    new WorkstationHelper.VoidDelegateProcess() {
                        @Override
                        public void executeRequest(Workspace workspace, List<FilePath> localPaths) throws TFSException {
                            ItemSpec[] itemSpecs = TFSFileUtil.getItemSpecs(myProject, workspace, localPaths, true, false);
                            PendingSet pendingSet = workspace.getPendingChanges(itemSpecs, false);
                            PendingChange[] pendingChanges = pendingSet.getPendingChanges();

                            List<FilePath> revertImmediatelty = new ArrayList<FilePath>();
                            List<FilePath> pathsToProcess = new ArrayList<FilePath>(localPaths);
                            for(int i = 0; i < pendingChanges.length; i++){
                                PendingChange pendingChange = pendingChanges[i];
                                ChangeType changeType = pendingChange.getChangeType();
                                if (changeType.containsAny(ChangeType.ADD.combine(ChangeType.UNDELETE))){
                                    FilePath localPath = TFSFileUtil.getFilePath(pendingChange.getLocalItem());

                                    revertImmediatelty.add(localPath);
                                    excludeFromFurtherProcessing(localPath);

                                    pathsToProcess.remove(localPath.getPath());
                                }
                            }

                            Collection<VcsException> undoErrors = SharedOperations.performUndo(myProject, workspace,
                                    revertImmediatelty, null);
                            if (!undoErrors.isEmpty())
                                AbstractVcsHelper.getInstance(myProject).showErrors(new ArrayList(undoErrors),
                                        TFSVcs.VCS_NAME);

                            StatusProvider.visitByStatus_V2(workspace, pathsToProcess, false, null,
                                    myProject, null, deleteExecuteStatusVisitor);
                        }
                    });
        } catch (TFSException e) {
            AbstractVcsHelper.getInstance(this.myProject).showError(new VcsException(e), TFSVcs.VCS_NAME);
        }
        if (!myDeletedFiles.isEmpty() || !myDeletedWithoutConfirmFiles.isEmpty())
            super.executeDelete();
    }

    @Override
    protected void performDeletion(List<FilePath> filesToDelete) {
        final List<VcsException> errors = new ArrayList<>();
        try{
            WorkstationHelper.processByWorkspaces(filesToDelete, false, myProject,
                    new WorkstationHelper.VoidDelegateProcess() {
                        @Override
                        public void executeRequest(Workspace workspace, List<FilePath> localPaths) throws TFSException {
                            Collection<VcsException> scheduleErrors = SharedOperations.scheduleForDeletion(myProject,
                                    workspace, localPaths, null);
                            errors.addAll(scheduleErrors);
                        }
                    });
        } catch (TFSException e) {
            errors.add(new VcsException(e));
        }
        if (!errors.isEmpty())
            AbstractVcsHelper.getInstance(myProject).showErrors(errors, TFSVcs.VCS_NAME);
    }

    private List<MovedFileInfo> myMovedFiles;

    @Override
    protected void performMoveRename(final List<MovedFileInfo> movedFiles) {

       final Map<FilePath, FilePath> movedPaths = new HashMap<>(movedFiles.size());
        for(MovedFileInfo movedFileInfo : movedFiles){
            FilePath oldFile = VcsUtil.getFilePath(movedFileInfo.myOldPath);
            movedPaths.put(oldFile,
                    VcsUtil.getFilePath(movedFileInfo.myNewPath));

            //myDeletedFiles.remove(oldFile);
        }
        myMovedFiles = movedFiles;

        final List<VcsException> errors = new ArrayList<>();
        //final Map<FilePath, FilePath> scheduleMove = new HashMap<>();

        try{
            WorkstationHelper.processByWorkspaces(movedPaths.keySet(), false, myProject,
                    new WorkstationHelper.VoidDelegateProcess() {

                        @Override
                        public void executeRequest(Workspace workspace, List<FilePath> localPaths) throws TFSException {

                            errors.addAll(SharedOperations.scheduleRename(myProject, workspace, localPaths, movedPaths));

                        }
                    });

        } catch (TFSException e) {
            errors.add(new VcsException(e));
        }
        if (!errors.isEmpty())
            AbstractVcsHelper.getInstance(myProject).showErrors(errors, TFSVcs.VCS_NAME);
    }

    private void excludeFromFurtherProcessing(FilePath localPath) {
        if (!this.myDeletedFiles.remove(localPath))
            this.myDeletedWithoutConfirmFiles.remove(localPath);
    }

    @Override
    protected boolean isDirectoryVersioningSupported() {
        return true;
    }

    @Override
    protected void beforeContentsChange(VirtualFileEvent event, VirtualFile file) {
        //Here we should be able to check if the file is checked out or not but,
        //we really only need to do that on a local workspace.

        if (!TFSVcs.isIgnored(myProject, file) && event.isFromSave()){
            CheckoutFileIfNeededTask task = new CheckoutFileIfNeededTask(myProject, file);
            ProgressManager.getInstance().run(task);
        }else if(!TFSVcs.isIgnored(myProject, file)){
            log.debug(event.toString());
        }
    }

    private StatusVisitor addExecuteStatusVisitor = new StatusVisitor(){


        @Override
        protected void performNotVersion(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performDeleted(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performScheduledForAddition(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {
            myAddedFiles.remove(filePath.getVirtualFile());
        }

        @Override
        protected void performScheduledForDeletion(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performCheckedOutForEdit(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performOutOfDate(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performUpToDate(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performRenamed(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performRenamedCheckedOut(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performUndeleted(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performIgnored(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }
    };

    private StatusVisitor deleteExecuteStatusVisitor = new StatusVisitor() {

        @Override
        protected void performNotVersion(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {
            excludeFromFurtherProcessing(filePath);
        }

        @Override
        protected void performDeleted(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {
            excludeFromFurtherProcessing(filePath);
        }

        @Override
        protected void performScheduledForAddition(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {
            TFSVcs.error("Cannot revert an item scheduled for addition: " + filePath.getPresentableUrl());
        }

        @Override
        protected void performScheduledForDeletion(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {
            excludeFromFurtherProcessing(filePath);
        }

        @Override
        protected void performCheckedOutForEdit(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performOutOfDate(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performUpToDate(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performRenamed(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performRenamedCheckedOut(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {

        }

        @Override
        protected void performUndeleted(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {
            TFSVcs.error("Cannot revert undeleted: " + filePath.getPresentableUrl());
        }

        @Override
        protected void performIgnored(FilePath filePath, boolean existsLocally, ServerStatus serverStatus) {
            excludeFromFurtherProcessing(filePath);
        }
    };


}
